package service;

import java.util.Date;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import exceptions.MatchRestrictionException;

import model.League;
import model.Match;
import model.User;

/**
 * Class that manipulates matchs in the league.
 * @author Andre Melo
 * @author Marcus Leite 
 * @author Sergio Danilo
 *
 */
public class MatchServiceImpl implements MatchService {
	
	/*
	 * The Entity Manager, responsible for the operations with the database.
	 */
	@PersistenceContext
	private EntityManager em;
	
	/*
	 * The instance of LeagueService.
	 */
	private LeagueService leagueService;
	
	/*
	 * The instance of UserService.
	 */
	private UserService userService;
	
	@Override
	public Integer addMatchResult(Integer leagueId, Date date, Integer idWinner, Integer idLoser) throws MatchRestrictionException {
		verifySameUsersMatch(idWinner, idLoser);
		League league = leagueService.findById(leagueId);
		User winner = userService.findById(idWinner);
		User loser = userService.findById(idLoser);
		league.setNumberOfMatches();
		Match match = new Match(league, date, winner, loser);
		match.setMatchIndex(league.getNumberOfMatches());
		Integer id = save(match);
		return id;
	}
	
	@Override
	public Integer addMatchResult(Integer leagueId, Date date,
			Integer idWinner, Integer idLoser, Integer length, Integer score,
			Integer longestRunForWinner, Integer longestRunForLoser) throws MatchRestrictionException {
		verifyAddMatchResult(length, score, longestRunForWinner, longestRunForLoser);
		verifySameUsersMatch(idWinner, idLoser);
		League league = leagueService.findById(leagueId);
		User winner = userService.findById(idWinner);
		User loser = userService.findById(idLoser);
		league.setNumberOfMatches();
		Match match = new Match(league, date, winner, loser, length, score, longestRunForWinner, longestRunForLoser);
		match.setMatchIndex(league.getNumberOfMatches());
		Integer id = save(match);
		return id;
	}

	/*
	 * Verify if two user not match
	 * @param idWinner The ID of the winner
	 * @param idLoser The ID of the loser
	 * @throws MatchRestrictionException If two user not macth, the error message will be throws
	 */
	private void verifySameUsersMatch(Integer idWinner, Integer idLoser) throws MatchRestrictionException {
		if (idWinner.compareTo(idLoser) == 0)
			throw new MatchRestrictionException("Users must be different");
		
	}

	/*
	 * Verify if attributes of the match are received correctly
	 * @param length The length of the match cannot be less or equal than zero (0) 
	 * @param score The score of the match cannot be less than 0 and more than length
	 * @param longestRunForWinner This attribute cannot be less than 1 and more than length
	 * @param longestRunForLoser This attribute cannot be less than 0 and more than score
	 * @throws MatchRestrictionException
	 */
	private void verifyAddMatchResult(Integer length, Integer score, Integer longestRunForWinner, Integer longestRunForLoser) throws MatchRestrictionException{
		if (length <= 0)
			throw new MatchRestrictionException("Invalid match length");
		if (score < 0 || score >= length)
			throw new MatchRestrictionException("Invalid score");
		if (longestRunForWinner < 1 || longestRunForWinner > length)
			throw new MatchRestrictionException("Invalid run");
		if (longestRunForLoser <0 || longestRunForLoser > score)
			throw new MatchRestrictionException("Invalid run");
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public void deleteAll() {
		Query query = em.createQuery("Select m From Match m");
		List<Match> matchList = query.getResultList();
		int listSize = matchList.size();
		for (int i=0;i<listSize; i++){
			matchList.get(i).getLeague().setNumberOfMatches(0);
			em.remove(matchList.get(i));
		}		
	}

	@Override
	public void deleteMatch(Integer matchId) {
		Match match = findById(matchId);
		League league= match.getLeague();
		Integer newNumberOfMatches = league.getNumberOfMatches() - 1;
		league.setNumberOfMatches(newNumberOfMatches);
		em.remove(match);
	}
	
	@Override
	public Match findById(Integer id) {
		return em.find(Match.class, id);
	}

	@Override
	public LeagueService getLeagueService() {
		return leagueService;
	}

	/*
	 * Get match by the ID
	 * @param matchId The match's ID
	 * @return Match by the ID
	 */
	@SuppressWarnings("unchecked")
	private Match getMatch(Integer matchId){
		Query query = em.createQuery("Select m From Match m Where m.id = :id");
		query.setParameter("id",matchId);
		List<Match> matches = query.getResultList();
		return matches.get(0);
	}

	@SuppressWarnings("unchecked")
	@Override
	public Integer getMatch(Integer leagueId, Integer matchIndex) throws MatchRestrictionException {
		League league = leagueService.findById(leagueId);
		if (matchIndex < 1 || league.getNumberOfMatches() < matchIndex){
			throw new MatchRestrictionException("Invalid index");
		}
		Query query = em.createQuery("Select m From Match m Where m.league.id = :leagueId Order By m.date");
		query.setParameter("leagueId", leagueId);
		List<Match> matches = query.getResultList();
		return matches.get(matchIndex-1).getId();
	}

	@SuppressWarnings("unchecked")
	@Override
	public Integer getMatch(Integer userId, Integer leagueId, Integer index) throws MatchRestrictionException {
		League league = leagueService.findById(leagueId);
		if (index < 1 || league.getNumberOfMatches() < index){
			throw new MatchRestrictionException("Invalid index");
		}
		Query query = em.createQuery("Select m From Match m Order By m.date");
		List<Match> matches = query.getResultList();
		Integer count = 1;
		for (Match match : matches) {
			if (match.getLeague().getId().compareTo(leagueId) == 0){
				if (match.getWinner().getId().compareTo(userId) == 0 || match.getLoser().getId().compareTo(userId) == 0){
					if (count.compareTo(index) == 0)
						return match.getId();
					count++;
				}
			}
		}
		return null;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Integer getMatchByDate(Integer leagueId, Date mdStart, Date mdEnd, Integer matchIndex) throws MatchRestrictionException{
		League league = leagueService.findById(leagueId);
		if (matchIndex < 1 || league.getNumberOfMatches() < matchIndex)
			throw new MatchRestrictionException("Invalid index");
		Query query = em .createQuery("Select m From Match m Where m.league.id = :league and m.date >= :mdStart and m.date <= :mdEnd Order By m.date");
		query.setParameter("league",leagueId);
		query.setParameter("mdStart",mdStart);
		query.setParameter("mdEnd",mdEnd);
		List<Match> matches = query.getResultList();
		return matches.get(matchIndex-1).getId();		
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public Integer getMatchByDate(Integer userId, Integer leagueId, Date mdStart, Date mdEnd, Integer matchIndex) throws MatchRestrictionException{
		League league = leagueService.findById(leagueId);
		if (matchIndex < 1 || league.getNumberOfMatches() < matchIndex)
			throw new MatchRestrictionException("Invalid index");
		Query query = em .createQuery("Select m From Match m Where m.league.id = :league and (m.winner.id = :userId or m.loser.id = :userId) and m.date >= :mdStart and m.date <= :mdEnd Order By m.date");
		query.setParameter("league",leagueId);
		query.setParameter("userId", userId);
		query.setParameter("mdStart",mdStart);
		query.setParameter("mdEnd",mdEnd);
		List<Match> matches = query.getResultList();
		return matches.get(matchIndex-1).getId();
	}
	
	@Override
	public Integer getMatchLength(Integer matchId) {
		return getMatch(matchId).getLenght();
	}
	
	@Override
	public Integer getMatchLongestRunForLoser(Integer matchId){
		return getMatch(matchId).getLongestRunForLoser();
	}

	@Override
	public Integer getMatchLongestRunForWinner(Integer matchId){
		return getMatch(matchId).getLongestRunForWinner();
	}
	
	@Override
	public User getMatchLoser(Integer matchId) {
		return getMatch(matchId).getLoser();
	}

	@Override
	public Integer getMatchScore(Integer matchId) {
		return getMatch(matchId).getScore();
	}
	
	@Override
	public User getMatchWinner(Integer matchId) {
		return getMatch(matchId).getWinner();
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public Integer getNumberOfLeagueMatches(Integer leagueId) {
		Query query = em.createQuery("Select m From Match m");
		List<Match> matches = query.getResultList();
		Integer counter = 0;
		for (Match match : matches) {
			if (match.getLeague().getId().equals(leagueId))
				counter++;
		}
		return counter;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Integer getNumberOfLosses(Integer userId, Integer leagueId){
		Query query = em.createQuery("Select m From Match m");
		List<Match> matches = query.getResultList();
		Integer counter = 0;
		for (Match match : matches) {
			if (match.getLeague().getId().compareTo(leagueId) == 0){
				if (match.getLoser().getId().equals(userId))
					counter++;
			}
		}
		return counter;
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public Integer getNumberOfPlayerMatches(Integer userId, Integer leagueId) {
		Query query = em.createQuery("Select m From Match m");
		
		List<Match> matches = query.getResultList();
		Integer counter = 0;
		for (Match match : matches) {
			if (match.getLeague().getId().compareTo(leagueId) == 0){
				if (match.getWinner().getId().equals(userId) || match.getLoser().getId().equals(userId))
					counter++;
			}
		}
		return counter;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Integer getNumberOfWins(Integer userId, Integer leagueId) {
		Query query = em.createQuery("Select m From Match m");
		List<Match> matches = query.getResultList();
		Integer counter = 0;
		for (Match match : matches) {
			if (match.getLeague().getId().compareTo(leagueId) == 0){
				if (match.getWinner().getId().equals(userId))
					counter++;
			}
		}
		return counter;
	}

	@Override
	public UserService getUserService() {
		return userService;
	}

	@Override
	public Integer save(Match match) {
		em.persist(match);
		return match.getId();
	}
	
	@Override
	public void setLeagueService(LeagueService leagueService) {
		this.leagueService = leagueService;
	}
	
	@Override
	public void setUserService(UserService userService) {
		this.userService = userService;
	}
	
	@Override
	public void update(Match match) {
		em.merge(match);
	}

	@Override
	public void updateMatchResult(Integer matchId, Date date,
			Integer idWinner, Integer idLoser, Integer length, Integer score,
			Integer longestRunForWinner, Integer longestRunForLoser) throws MatchRestrictionException {
		verifyAddMatchResult(length, score, longestRunForWinner, longestRunForLoser);
		verifySameUsersMatch(idWinner, idLoser);
		User winner = userService.findById(idWinner);
		User loser = userService.findById(idLoser);
		Match match = findById(matchId);
		match.setDate(date);
		match.setWinner(winner);
		match.setLoser(loser);
		match.setLenght(length);
		match.setScore(score);
		match.setLongestRunForWinner(longestRunForWinner);
		match.setLongestRunForLoser(longestRunForLoser);
		update(match);
	}

	@Override
	public Date getMatchDate(Integer matchId) {
		return getMatch(matchId).getDate();
	}

	@SuppressWarnings("unchecked")
	@Override
	public String getMatchAsString(Integer userId, Integer leagueId,
			Integer index) throws MatchRestrictionException {
		Integer matchId = getMatch(userId, leagueId, index);
		Match matchAux = getMatch(matchId);
		Query query = em.createQuery("Select m From Match m Where m.matchIndex = :matchIndex");
		query.setParameter("matchIndex", matchAux.getMatchIndex());
		List<Match> match = query.getResultList();
		String result = "";
		if (match.get(0).getWinner().getId().equals(userId))
			result  = "date="+match.get(0).getDate()+",opponent="+match.get(0).getLoser().getFirstName()+" "+match.get(0).getLoser().getLastName()+
			                ",myScore="+match.get(0).getLenght()+",opponnentScore="+match.get(0).getScore()+
			                ",myRun="+match.get(0).getLongestRunForWinner()+",opponentRun="+match.get(0).getLongestRunForLoser()+
			                ",myHandicap="+match.get(0).getWinner().getHandicap()+",winStatus=true";
		else result  = "date="+match.get(0).getDate()+",opponent="+match.get(0).getWinner().getFirstName()+" "+match.get(0).getWinner().getLastName()+
        				",myScore="+match.get(0).getScore()+",opponnentScore="+match.get(0).getLenght()+
        				",myRun="+match.get(0).getLongestRunForLoser()+",opponentRun="+match.get(0).getLongestRunForWinner()+
        				",myHandicap="+match.get(0).getLoser().getHandicap()+",winStatus=false";
		return result;
	}

	
}

